import pygame
import ConfigParser
import Util
import sys

def instantiate(vars_dict, **args):
   if len(args) > 0:
      vars_dict = vars_dict.copy()
      vars_dict.update(args)
   cls = vars_dict['type']
   return cls(**dict([(k, v) for k,v in vars_dict.items() if k != 'type']))

class Configurable(object):
   """Generalized class representing an object that can be created from a
      config file."""
   
   __slots__ = ('name',
                'description')
   
   def __init__(self, vars_dict):
      cfg = self.__class__.config_vars()
      if 'args' in vars_dict:
         vars_dict.update(vars_dict['args'])
         del vars_dict['args']
      for key in vars_dict.keys():
         if key in cfg and key != 'args':
            setattr(self, key, vars_dict[key])
   
   @classmethod
   def check_missing(cls, vars_dict):
      required = cls.required_variables()
      missing = set(required) - set(vars_dict.keys())
      if len(missing) > 0:
         Util.error("%s object %s is missing required config variables: %s" % 
                    (cls.__name__, vars_dict['name'], ', '.join(missing)))
   
   @classmethod
   def config_vars(cls):
      """Returns a list of all the variables this class accepts as config
         parameters."""
      bases = cls.__bases__
      vars = list()
      for b in bases:
         if b is not Configurable:
            if 'config_vars' in dir(b):
               vars.extend(b.config_vars())
         else:
            vars.extend(Configurable.__slots__)
      return vars + [v for v in cls.__slots__ if v not in cls.runtime_vars]
   
   def get_description(self):
      return self.description
   
   def get_name(self):
      return self.name
   
   @classmethod
   def from_file(cls, items):
      """Given a list of (key, value) pairs, returns an item initialized to
         the values."""
      vars_dict = dict()
      vars = cls.config_vars()
      for key, val in items:
         if key in vars:
            vars_dict[key] = Util.str_convert(val)
      missing = cls.check_missing(vars_dict)
      vars_dict['type'] = cls
      return vars_dict
   
   @classmethod
   def init_vars(cls):
      num_args = cls.__init__.func_code.co_argcount
      return cls.__init__.func_code.co_varnames[1:num_args]

   @classmethod
   def many_from_file(cls, config_file):
      """Returns a dict mapping the names of sections in a config file to the
         objects generated from the sections."""
      cf = ConfigParser.RawConfigParser()
      cf.read(config_file)
      objs_dict = dict()
      for section in cf.sections():
         items = cf.items(section)
         items.append(['name', section])
         objs_dict[section] = cls.from_file(items)
      return objs_dict

   @classmethod
   def optional_variables(cls):
      return cls.variables()[1]

   @classmethod
   def required_variables(cls):
      return cls.variables()[0]
   
   @classmethod
   def variables(cls):
      bases = [b for b in cls.__bases__ if (issubclass(b, Configurable) and b is not Configurable)]
      required = list()
      optional = list()
      for b in bases:
         vars = b.variables()
         required.extend(vars[0])
         optional.extend(vars[1])
      defaults = cls.__init__.func_defaults
      if defaults is not None:
         num_optional = len(defaults)
      else:
         num_optional = 0
      num_args = cls.__init__.func_code.co_argcount
      if num_args > 1:
         args = cls.__init__.func_code.co_varnames[1:num_args]
         required.extend(args[:len(args) - num_optional])
         optional.extend(args[len(args) - num_optional:])
      return [required, optional]
      
